三角函數是遊戲程式設計師必定要鑽研的課題之一,除了在向量的運算中需要大量的三角函數之外,光是正弦(sine)與餘弦(cosine)這兩個數學式本身,就能帶給遊戲莫大的好處。我們今天就來談談一些sine與cosine的應用。
我知道同學們對這些基本的三角函數都很熟了,不過還是在這裏快速介紹一下什麼是正弦函數和餘弦函數。如果一個直角三角形的斜邊長度是1,其中一個銳角是θ(唸作theta),那麼在這個角對面的邊長長度就是正弦sin(θ),另一個在角旁邊的底邊長度就是餘弦cos(θ)。
在JavaScript/TypeScript中,正弦函數可由Math.sin(θ)計算出來,其中的θ單位是弧度(角度180度=弧度π),餘弦函數則是由Math.cos(θ)來計算。這兩個函數有以下一些明顯的特性,
如果我們把三角形長度為1的斜邊從θ為0°轉到90°、180°,再繼續轉到270°、360°,就會發現這個斜邊滑過的面積剛好是一個圓,而圓周上的點,剛好可以用 x = cos(θ) 和 y = sin(θ) 來表示,這也就是極座標和xy座標轉換公式的來源。
/** 將極座標轉換成x,y座標的函式,參數依序是
* length: 距離原心的距離
* angle: 和x軸的夾角(以弧度表示)
*/
function polarToXY(length: number, angle: number): Point {
return new Point(
Math.cos(angle) * length, // x值
Math.sin(angle) * length // y值
);
}
如果在一個座標平面把θ的變化放在x軸,sin(θ)和cos(θ)放在y軸,那畫出來的就是波的形狀。
大致瞭解了sine與cosine的特性之後,我們就能來看看這兩個函數到底能怎樣被遊戲拿來運用。
記得剛剛我們講的,把直角三角形的斜邊繞了360度畫出一個圓形嗎?圓上點的座標可以用(cos(θ) ,sin(θ) )來表示,其中不管是x值的變化,或是y值的變化,都會呈現活塞運動的軌跡。
那麼,如果我們把時間作為θ放進三角函數算座標,然後只把圓上座標的y值拿出來使用,就可以模擬小魔女飛在空中上下飄浮的動畫。
在俯視地圖中,我們也可以把三角函數放入武器的縮放值,來營造物體飄浮的感覺。
/** 模擬小魔女在空中飄浮
* witch: 魔女的圖案
* center: 魔女飄浮的中心點
* time: 目前的時間(毫秒)
*/
function updateWitch(witch: PIXI.Sprite, center: Point, time: number) {
// 飄浮上下震動的頻率(次數/毫秒)
let frequency = 0.001;
// 飄浮上下震動的震幅(次數/毫秒)
let yHalfRange = 30;
// 以目前的時間和頻率計算現在的θ
let theta = frequency * time * Math.PI * 2;
// 更新魔女的y
witch.y = center.y + Math.sin(theta) * yHalfRange;
}
類似於活塞運動的應用,但只取θ在0°到90°(π)區間內的sin(θ),就可以把線性的數值變化改成淡出(即越靠近動畫的尾聲,值改變地越少)。
我們來寫寫程式,把淡出的數學寫成一個函式。
/** 寫一個淡出的百分比變化
* 參數是目前經過的時間 time
* 以及整個動畫的時間 duration
* 回傳一個0到1的數值,這個數值的變化會有淡出的效果
*/
function fadeout(time: number, duration: number): number {
// 先算出線性的百分比值
let percent = time / duration;
// 限制這個比值在0到1的區間
percent = Math.max(0, Math.min(1, percent));
// 使用sin讓percent轉變為淡出(Math.PI/2就是90°)
return Math.sin(percent * Math.PI / 2);
}
如果θ取在-90°(-π)到0°之間,那數值的變化就可以拿來製作淡入效果,不過因為值會從-1跑到0,所以在應用的時候,要把sin(θ)的值加上1,調整為0到1。
/** 淡入的百分比變化
* 參數是目前經過的時間 time
* 以及整個動畫的時間 duration
* 回傳一個0到1的數值,這個數值的變化會有淡入的效果
*/
function fadein(time: number, duration: number): number {
// 先算出線性的百分比值
let percent = time / duration;
// 限制這個比值在0到1的區間
percent = Math.max(0, Math.min(1, percent));
// 使用sin讓percent轉變為淡出(θ要移-90°)
let value = Math.sin(percent * Math.PI / 2 - Math.PI / 2);
// 最後的值要加上1,才會讓值從0變化至1
return value + 1;
}
同理,θ取在-90°(-π)到90°(π)之間,那就可以淡入後淡出,但是在取得sin(θ)值之後,要加上1再除以2,才能將值調整至0到1。
/** 淡入且淡出的百分比變化
* 參數是目前經過的時間 time
* 以及整個動畫的時間 duration
* 回傳一個0到1的數值,這個數值的變化會有淡入且淡出的效果
*/
function fadeinAndOut(time: number, duration: number): number {
// 先算出線性的百分比值
let percent = time / duration;
// 限制這個比值在0到1的區間
percent = Math.max(0, Math.min(1, percent));
// 使用sin讓percent轉變為淡出(θ要移-90°)
let value = Math.sin(percent * Math.PI - Math.PI / 2);
// 最後的值要加1再除以2,才會讓值從0變化至1
return (value + 1) / 2;
}
三角函數的運用方式千變萬化,同學們可以進行各種實驗,試著把曲線變成畫面上的美麗波動吧。
如果想進一步看看還有什麼數學曲線可資利用,請參考拙作,
《數學妹子與遊戲漢子的相遇: (科普)Easing函式將遊戲可愛化了》